package ch.epfl.lara.pm.parser

import ch.epfl.lara.pm.ast._

import scala.util.parsing.input.Reader
import scala.util.parsing.combinator.{ImplicitConversions,~}
import scala.util.parsing.combinator.syntactical.StdTokenParsers
import scala.util.parsing.combinator.lexical.StdLexical
import scala.util.parsing.syntax.StdTokens
import scala.util.parsing.input.StreamReader

import java.io.{File,FileInputStream,InputStreamReader,StringReader}

class MyLexical extends StdLexical {
  import scala.util.parsing.input.CharArrayReader.EofCh
  override def whitespace: Parser[Any] = rep(
      whitespaceChar
      //| '/' ~ '*' ~ comment
      | '/' ~ '/' ~ rep( chrExcept(EofCh, '\n') )
      //| '/' ~ '*' ~ failure("unclosed comment")
      )
}

trait NScalaParser extends StdTokenParsers  with ImplicitConversions  {
  type Tokens = StdLexical // StdLexical is used to scan Scala-like languages
  val lexical = new MyLexical
  
  
  // Keywords
  lexical.reserved ++= List("constraints","of",
                            "object", "class", "extends", "abstract",            // class and object decl
                            "def", "unapply",                                    // methods decl
                            "val",                                               // fields visibility
                            "Option", "None",                                    // built-in type
                            "true","false",                                      // values
                            "new",                                               // create value
                            "if", "else", "match", "case",                       // constructs
                            "isInstanceOf",                                      // operator
                            "empty","x"
                           )

  // Delimiters                         
  lexical.delimiters ++= List("{","}","(",")","[","]","/*","*/",
      ",", ":", "=>", ".", "=",
      "+","-","*","/","%",
      "&&","||","<","<=","==","!=","<:","<",">=",
      "&","|")

  
  /** <pre>
   *    Program  ::= [ Constraint ] { ObjectDef } { ClassDef } Expr
   * </pre>
   */    
  def Prog: Parser[Program] = (
        rep(Constraint) ~ rep(ObjDef) ~ rep(ClsDef) ~ Expr     ^^ flatten4(Program)
      | failure("illegal start of program definition")
  )
  
  
  def Constraint: Parser[(String,Formula)] = (
        "/*" ~ "constraints" ~ "of" ~ ident ~ ":" ~ Form ~ "*/" ^^flatten2({(x,form) => (x,form)}) 
      | failure("illegal start of program definition")
  )
  
  
  def Form: Parser[Formula] = (
        Atom ~ rep( (  "&"  ^^ ({ () => "&"}) 
                     | "|"  ^^ ({ () => "|"})
                  ) ~ Form) ^^ flatten2(mkFormula _)
      | failure("illegal start of formula definition")
  )
  
  def Atom: Parser[Atom] = (
        "(" ~ Atom ~ ")"
      |  Term ~ "<:" ~ Term ^^ flatten2(SubsetEq)
      | failure("illegal start of predicate formula definition")  
  )
  
  def Term: Parser[Term] = (
        "(" ~ Term ~ ")" 
      |  Elem  ~ rep(( "+" ^^ ({() => "+"})
                     | "*" ^^ ({() => "*"})
                     ) ~ Elem) ^^ flatten2(mkTerm _)
      | failure("illegal start of atomic formula definition")
  )
  
  def Elem: Parser[Term] = (
        ident ^^ Set _//~ rep("x" ~ ident) ^^ flatten2({(x,xs) => xs match {case Nil =>Set(x) case ls => CartesianProduct(x :: ls)}})
      | "empty" ^^ EmptySet  
      | failure("illegal start of element formula definition")
  )
  
  
  def mkTerm(left: Term, opAndRight: List[~[()=>String,Term]]): Term = { 
     def opToTerm(op: () => String, left: Term, right: Term) = op() match {
       case "+" => Union(left,right)
       case "*" => Intersection(left,right)
     }
     opAndRight match {
       case Nil => left
       case ~(op,right) :: rest => mkTerm(opToTerm(op,left,right),rest)  
     } 
  }
  def mkFormula(left: Formula, opAndRight: List[~[() => String,Formula]]): Formula = {
     def opToFormula(op: () => String, left: Formula, right: Formula) = op() match {
       case "&" => And(left,right)
       case "|" => Or(left,right)
     }
     
     opAndRight match {
       case Nil => left
       case ~(op,right) :: Nil => opToFormula(op,left,right)
       case ~(op,right) :: rest => mkFormula(opToFormula(op,left,right),rest)
     } 
   }
  
  
  /** <pre>
   *    ObjectDef ::= "object" D  "{" 
   *                     ExtractorDef 
   *                  "}"
   *  </pre>
   */
  def ObjDef: Parser[ObjectDef] = (
        "object" ~ ident ~ "{" ~ ExtDef ~ "}"   ^^ flatten2(ObjectDef)
      | failure("illegal start of object definition")
  )
  
  
  /** <pre>
   *    ExtractorDef ::= "def" "unapply" "(" ParamList ) ":" ExtType "=" Expr
   *  </pre>
   */
  def ExtDef: Parser[ExtractorDef] = (
        "def" ~ "unapply" ~ "(" ~ ParDef ~ ")" ~ ":" ~ ExtType ~ "=" ~ Expr ^^ flatten3(ExtractorDef _)
      | failure("illegal start of extractor definition")
  )
  
  def ExtType: Parser[TypeTree] = (
        "Option" ~ "[" ~ ( TypeTuple | Type ) ~ "]" ^^ ({x => x match {case e: TypeTree => OptionType(List(e))
                                                                       case _ => OptionType(x.asInstanceOf[List[TypeTree]]) 
                                                       }})
      | Type
      | failure("illegal start of extractor return type definition")
  )
  
  def TypeList: Parser[List[TypeTree]] = (
        Type ~ rep( "," ~ Type)  ^^  flatten2({(x,xs) => x :: xs})
      | success(Nil: List[TypeTree])
  )
  
  def TypeTuple: Parser[List[TypeTree]] = (
        "(" ~ Type ~  rep1( "," ~ Type) ~")" ^^ flatten2({(x,xs) => x :: xs})
      | failure("illegal start of tuple type definition")
  )
  
  def VarList: Parser[List[String]] = (
        ident ~ rep( "," ~ ident)  ^^  flatten2({(name,xs) => name :: xs})
      | success(Nil: List[String])
  )
  
  /** <pre>
   *     ClassDef ::= "class" C "(" ParamList ")" "extends" D "{" 
   *                    { MethodDef } 
   *                  "}"
   *  </pre>
   */
  def ClsDef: Parser[ClassDef] = (
      "abstract" ~ "class" ~ ident ~ "(" ~ FieldList ~ ")" ~ "extends" ~ ident ~ "{" ~ rep(MethDef) ~"}"      ^^ flatten4((name,fields,superc,methods) => ClassDef(true,name,fields,superc,methods))
    |  "class" ~ ident ~ "(" ~ FieldList ~ ")" ~ "extends" ~ ident ~ "{" ~ rep(MethDef) ~"}"      ^^ flatten4((name,fields,superc,methods) => ClassDef(false,name,fields,superc,methods))
    | failure("illegal start of class definition")
  )
  
  /** <pre>
   *    MethodDef ::= "def" ident "(" ParamList ")" ":" T "=" Expr
   *  </pre>
   */
  def MethDef: Parser[MethodDef] = (
        "def" ~ ident ~ "(" ~ ParamList ~ ")"  ~ ":" ~ Type ~ "=" ~ Expr ^^ flatten4(MethodDef)
      | failure("illegal start of method definition")
  )
  
  
  /** <pre>
   *    ParamDef ::= ident ":" ident
   *  </pre>
   */
  def ParDef: Parser[ParamDef] = (
        ident ~ ":" ~ Type ^^ flatten2(ParamDef)
      | failure("illegal start of parameter definition")   
  )
  
  
  /** <pre>
   *    ParamList ::= ParamDef { "," ParamDef }
   *  </pre>
   */
  def ParamList: Parser[List[ParamDef]] = (
        ParDef ~ rep("," ~ ParDef ) ^^ flatten2( { (param, params) => param :: params } )
      | success(Nil: List[ParamDef])
  )
  
  def FieldDef: Parser[ParamDef] = (
      "val"~ ParDef 
    | failure("illegal start of field definition")
  )
  
  /** <pre>
   *    ParamList ::= ParamDef { "," ParamDef }
   *  </pre>
   */
  def FieldList: Parser[List[ParamDef]] = (
        FieldDef ~ rep("," ~ FieldDef ) ^^ flatten2( { (param, params) => param :: params } )
      | success(Nil: List[ParamDef])
  )
  
  def Type: Parser[TypeTree] = (
        ident ^^ toType _
      | failure("illegal start of type declaration")
  )
  
  
  /** <pre>
   *    Expressions ::=   Expr { "," Expr }
   	  	        | (empty)
   *  </pre>
   */
  def Expressions: Parser[List[Expr]] = (
      Expr ~ rep("," ~ Expr)  ^^ flatten2({ (e, es) => e :: es })
    | success(Nil: List[Expr])
  )
  
  
  /** <pre>
   *    Expr ::= OrExpr
   *  </pre>
   */
  def Expr: Parser[Expr] = (
         OrExpr
      |  failure("illegal start of expression")                 
  )
    
  /** <pre>
   *    OrExpr ::= AndExpr [ "||" OrExpr]
   *  </pre>
   */
  def OrExpr: Parser[Expr] = (
        AndExpr ~ opt("||" ~ OrExpr) ^^ flatten2({(and, or) => or match {case None => and case Some(x) => EOr(and,x)}})
      | failure("illegal start of <or> expression")
  )
  
  /** <pre>
   *    AndExpr ::= CmpExpr [ "&&" AndExpr]
   *  </pre>
   */
  def AndExpr: Parser[Expr] = (
  	CmpExpr ~ opt("&&" ~ AndExpr) ^^ flatten2({(cmp, and) => and match {case None => cmp case Some(x) => EAnd(cmp,x)}})
      | failure("illegal start of <and> expression")
  )

  
  /** <pre>
   *   CmpExpr ::= ArithExpr [ ( "<" | "<=" | "==" | "!=" ) ArithExpr ]
   *  </pre>
   */
  def CmpExpr: Parser[Expr] = ( 
        ArithExpr ~ opt( (   "<"   ^^ createCmpExpr("<")  _  
                           | "<="  ^^ createCmpExpr("<=") _
                           | ">"   ^^ createCmpExpr(">") _
                           | ">="  ^^ createCmpExpr(">=") _
                           | "=="  ^^ createCmpExpr("==") _
                           | "!="  ^^ createCmpExpr("!=") _
                         ) ~ ArithExpr
                       ) ^^ flatten2({(lhs,opt) => opt match {case None => lhs case Some(~(op,rhs)) => op(lhs,rhs)}})
      | failure("illegal start of boolean expression")
  )   
  
  
  /**
   * The op determines which object has to be instantiated
   */
  private def createCmpExpr(op: String)(lhs: Expr,rhs:Expr) = op match {
     case "<" => Less(lhs,rhs)
     case "<=" => LessEq(lhs,rhs)
     case ">" => Less(rhs,lhs)
     case ">=" => LessEq(rhs,lhs)
     case "==" => Eq(lhs,rhs)
     case "!=" => NotEq(lhs,rhs)
   }        
  
   
  /** <pre>
   *   ArithExpr ::= MatchExpr [ ( "+" | "-" | "%" | "/" | "*" ) MatchExpr ]
   *  </pre>
   */
  def ArithExpr: Parser[Expr] = (
       MatchExpr ~ rep( (   "+" ^^ (() => "+") 
                           | "-" ^^ (() => "-") 
                           | "%" ^^ (() => "%") 
                           | "/" ^^ (() => "/") 
                           | "*" ^^ (() => "*") 
                          )  ~ MatchExpr
                       ) ^^ flatten2(mkArithExpr _)  
     | failure("illegal start of arithmetic expression")
  )
  
      
  /** <pre>
   *    MatchExpr ::=  ComplexExpr [ "match" "{" { CaseDef } "}" ]
   *  </pre>
   */
  def MatchExpr: Parser[Expr] = (
      "(" ~ ComplexExpr ~ ":" ~ ident ~ ")" ~ "match" ~ "{" ~ rep(CseDef) ~ "}"  ^^ 
        flatten3({(scrut,tpe,cases) => Match(scrut,toType(tpe),cases.zipWithIndex.map(pair => CaseDef(pair._2, pair._1._1, pair._1._2, pair._1._3)))})
      | ComplexExpr ~ opt( "." ~ "isInstanceOf" ~"[" ~ ident ~ "]") ^^ flatten2({(e,tpe) => tpe match {case None => e case Some(tpe) => IsInstanceOf(e,tpe)}}) 
      | failure("illegale start of pattern matching expresison")
  )
  
  
  /** <pre>
   *    CseDef ::= "case" PatDef [ "if" Expr ] => Expr
   *  </pre>
   */
  def CseDef: Parser[(PatternDef,Option[Expr],Expr)] = (
        "case" ~ PatDef ~ opt("if" ~ Expr) ~ "=>" ~ Expr  ^^ flatten3({(a,b,c) => Tuple3(a,b,c)})
      | failure("illegal start of case definition")    
  )
  
  
  /** <pre>
   *    PatDef ::= ident [ "(" Patist ")" ] 
   *  </pre>
   */
  def PatDef: Parser[PatternDef] = (
        ident ~ "(" ~ PatList ~ ")"  ^^ flatten2({(name,args) => ClassPattern(PatternName(name),args)}) // flatten2(mkPattern _)
      | ident ~ ":" ~ Type               ^^ flatten2({(name,tpe) => VarPattern(PatternName(name),tpe)})  
      | failure("illegal start of pattern definition")
  )
  
  
  /** <pre>
   *    PatList ::= PatDef { "," PatDef }	
   *  </pre>
   */
  def PatList: Parser[List[PatternDef]] = (
          PatDef ~ rep("," ~ PatDef)  ^^  flatten2({(pat, pats) => pat :: pats})
      | success(Nil: List[PatternDef])
  )
  
    
  /** <pre>
   *    ComplexExpr ::= SimpleExpr { "." ident [ "(" Expressions ")" ] }
   *  </pre>
   */
  def ComplexExpr: Parser[Expr] = (
        SimpleExpr ~  rep("." ~ ident ~ opt("(" ~ Expressions ~ ")")) ^^ flatten2(mkExpression _)
     |  failure("illegal start of expression")                 
  )  
  

  
  /** <pre>
   *    SimpleExpr ::=    ident
   *		       |  "new" ident "(" Expressions ")"
   *                   |  "(" Expr ")"
   *                   |  "if" "(" Expr ")" "{" Expr "}" "else" "{" Expr "}"
   *		       |  numericLit
   *  </pre>
   */
  def SimpleExpr: Parser[Expr] = (
        ident                                        ^^ Var _
      | "true" ^^True
      | "false" ^^False
      | "new" ~ ident ~ "(" ~ Expressions ~ ")"      ^^ flatten2(New _)
      | "None" ^^ INone
      | "(" ~ Expr ~ ")"
      | "if" ~ "(" ~ Expr ~ ")" ~ "{" ~ Expr ~ "}" ~ "else" ~ "{" ~ Expr ~ "}"  ^^ flatten3(If _)
      | opt("-") ~ numericLit ^^ flatten2({(neg,x) => neg match {case false=> Number(x.toInt) case _ => Number(-x.toInt)}}) 
      | failure("illegal start of simple expression")
  )
  
  
 /*
  def mkIdent(s: String) = s match {
     case "true" => True
     case "false" => False
     case _ => Var(s)
  }
  */
  def mkExpression(obj: Expr, rest: List[~[String, Option[List[Expr]]]]) = {
    var t: Expr = obj
    def buildPath(xs: List[~[String, Option[List[Expr]]]]): Unit = xs match {
      case ~(meth, Some(args)) :: rest =>
        t = Call(t, meth, args)
        buildPath(rest)
      case ~(field, None) :: rest =>
        t = Select(t, field)
        buildPath(rest)
      case Nil => ()
    }
    buildPath(rest)
    t
  }
 
  def mkArithExpr(left: Expr, opAndRight: List[~[()=>String,Expr]]): Expr = {
    def opToExpr(op: () =>String)(left: Expr,right:Expr) = op() match {
      case "+" => Plus(left,right)
      case "-" => Minus(left,right)
      case "%" => Mod(left,right)
      case "*" => Prod(left,right)
      case "/" => Div(left,right)
    }
    
    opAndRight match {
      case Nil => left
      case ~(op,right) :: Nil => opToExpr(op)(left,right)
      case ~(op1,right) :: (tail @ ~(op2,rright) :: rest) => op1() match {
        case "+" | "-" => opToExpr(op1)(left,mkArithExpr(right,tail))          // right-associative
        case "*" | "/" | "%" => mkArithExpr(opToExpr(op1)(left,right),tail)    // left-associative
      }
    }
  }
  
  def mkMatch(obj: Expr, tpe: String, mexpr: Option[List[CaseDef]]) = mexpr match {
    case Some(cases) => Match(obj,toType(tpe),cases) 
    case None => obj
  }
  
  
  def toType(tpe: String): TypeTree = tpe match {
    case "Int" => IntType
    case "Boolean" => BoolType
    case _ => ClassType(tpe)
  }
    
  private def parse(r: Reader[char]) : ParseResult[Tree] =
    phrase(Prog)(new lexical.Scanner(r))

  protected def parse(string: String) : ParseResult[Tree] =
    parse(StreamReader(new StringReader(string)))
    
  protected def parseFile(fileName: String) : ParseResult[Tree] =
    parse(StreamReader(new InputStreamReader(new FileInputStream(new File(fileName)), "ISO-8859-1")))
    
}
